/*
 * ThumbnailView.java
 *
 * Created on December 18, 2002, 4:29 PM
 */

package fr.umlv.cmis.listview;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.JComponent;
import javax.swing.ListSelectionModel;
import javax.swing.Scrollable;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.filechooser.FileSystemView;

import kiyut.imageio.ImageReaderWriterPreferences;
import kiyut.swing.shell.image.ImageUtilities;
import kiyut.swing.shell.util.ShellUtilities;

import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import fr.umlv.dnd.CmisObjectTransferHandler;
import fr.umlv.dnd.CustomDragGestureRecognizer;
import fr.umlv.dnd.CustomDropTargetListener;


// TODO: Auto-generated Javadoc
/**
 * The Class CmisThumbnailView.
 */
public class CmisThumbnailView extends JComponent implements Scrollable, ViewComponent {
   
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The Constant ICON_SIZE. */
	private static final int ICON_SIZE = 80;
	
	/** The property change listener. */
    private PropertyChangeListener propertyChangeListener;
    
    /** The flow layout. */
    private FlowLayout flowLayout;
    
    /** The column count. */
    private int columnCount = -1;
    
    /** The selection model. */
    protected ListSelectionModel selectionModel;
    
    /** The data model. */
    protected CmisObjectListViewModel dataModel;
    
    /** The cells. */
    protected List<CmisThumbnailCell> cells;
    
    /** The load list. */
    protected List<CmisObject> loadList;
    
    /** The cell editor. */
    protected ThumbnailCellEditor cellEditor;
    
    /** The editing index. */
    protected int editingIndex = -1;
    
    /** The background. */
    protected Color background;
    
    /** The selection background. */
    protected Color selectionBackground;
    
    /** The selection foreground. */
    protected Color selectionForeground;
    
    /** The drag enabled. */
    protected boolean dragEnabled = false;
    
    /** The thumbnail thread. */
    protected Thread thumbnailThread;
    
    /** The use cache. */
    protected boolean useCache = false;
    
    /** The cache directory. */
    protected File cacheDirectory = null;
    
    /** The md5. */
    protected MessageDigest md5;
    
    /** The image reader writer preferences. */
    protected ImageReaderWriterPreferences imageReaderWriterPreferences;
    
    /**
     * Instantiates a new cmis thumbnail view.
     *
     * @param dataModel the data model
     */
    public CmisThumbnailView(CmisObjectListViewModel dataModel) {
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception ex) {}
        
        cells = new ArrayList<CmisThumbnailCell>();
        loadList = Collections.synchronizedList(new ArrayList<CmisObject>());
        
        cellEditor = new ThumbnailCellEditor();
        cellEditor.addCellEditorListener(new javax.swing.event.CellEditorListener() {
            public void editingCanceled(ChangeEvent evt) { CmisThumbnailView.this.editingCanceled(evt); }
            public void editingStopped(ChangeEvent evt) { CmisThumbnailView.this.editingStopped(evt); }
        });
        
        
        this.dataModel = dataModel;
        dataModel.addListDataListener(new javax.swing.event.ListDataListener() {
            public void contentsChanged(ListDataEvent evt) { onContentsChanged(evt); }
            public void intervalAdded(ListDataEvent evt) { onIntervalAdded(evt); }
            public void intervalRemoved(ListDataEvent evt) { onIntervalRemoved(evt); }
        });
        
        // DnD Support
        setTransferHandler(new CmisObjectTransferHandler());
        ThumbnailViewDragGestureRecognizer dragRecognizer = new ThumbnailViewDragGestureRecognizer();
        addMouseListener(dragRecognizer);
        addMouseMotionListener(dragRecognizer);
        
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent evt) { calculatePreferredSize(); }
        });
        
        addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent evt) { onMouseClicked(evt); }
        });
        
        addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent evt) { onKeyPressed(evt); }
        });
        
        propertyChangeListener = new PropertyChangeHandler();
        addPropertyChangeListener(propertyChangeListener);
        
        flowLayout = new FlowLayout();
        flowLayout.setAlignment(FlowLayout.LEFT);
        setPreferredSize(new Dimension(128, 128));
        setLayout(flowLayout);
        //setOpaque(true);
        
        setBackground(UIManager.getColor("List.background"));
        setForeground(UIManager.getColor("List.foreground"));
        setFont(UIManager.getFont("Table.font"));
        setSelectionBackground(UIManager.getColor("List.selectionBackground"));
        setSelectionForeground(UIManager.getColor("List.selectionForeground"));
        
        thumbnailThread = new ThumbnailThread();
        thumbnailThread.start();
    }
    
    /* (non-Javadoc)
     * @see javax.swing.JComponent#setTransferHandler(javax.swing.TransferHandler)
     */
    public void setTransferHandler(TransferHandler newHandler) {
        super.setTransferHandler(newHandler);
        new ThumbnailViewDropTargetListener(this);
    }
    
    /**
     * Gets the selection background.
     *
     * @return the selection background
     */
    public Color getSelectionBackground() {
        return selectionBackground;
    }
    
    
    /**
     * Sets the selection background.
     *
     * @param selectionBackground the new selection background
     */
    public void setSelectionBackground(Color selectionBackground) {
        Color oldValue = this.selectionBackground;
        this.selectionBackground = selectionBackground;
        firePropertyChange("selectionBackground", oldValue, selectionBackground);
    }
    
    /**
     * Gets the selection foreground.
     *
     * @return the selection foreground
     */
    public Color getSelectionForeground() {
        return selectionForeground;
    }
    
    /**
     * Sets the selection foreground.
     *
     * @param selectionForeground the new selection foreground
     */
    public void setSelectionForeground(Color selectionForeground) {
        Color oldValue = this.selectionForeground;
        this.selectionForeground = selectionForeground;
        firePropertyChange("selectionForeground", oldValue, selectionForeground);
    }
    
    /* (non-Javadoc)
     * @see javax.swing.JComponent#setEnabled(boolean)
     */
    public void setEnabled(boolean b) {
        if (b == true) {
            synchronized(loadList) {
                loadList.notifyAll();
            }
        }
        super.setEnabled(b);
    }
    
    /**
     * Sets the drag enabled.
     *
     * @param b the new drag enabled
     */
    public void setDragEnabled(boolean b) {
        if (b && GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException();
        }
        dragEnabled = b;
    }
    
    /**
     * Gets the drag enabled.
     *
     * @return the drag enabled
     */
    public boolean getDragEnabled() {
        return dragEnabled;
    }
    
    /**
     * Refresh.
     */
    protected void refresh() {
        this.removeAll();
        this.repaint();
        
        selectionModel.clearSelection();
        cells.clear();
        loadList.clear();
        
        FileSystemView fsv = dataModel.getFileSystemView();
        
        for (int i=0; i < dataModel.getSize(); i++) {
            CmisObject file = (CmisObject)dataModel.getFile(i);
            CmisThumbnailCell cell = createCell(file,fsv);
            
            loadList.add(file);
            cells.add(cell);
            this.add(cell);
        }
        
        // set preferredSize
        calculatePreferredSize();
        revalidate();
        repaint();
        
        synchronized(loadList) {
            loadList.notifyAll();
        }
        
    }
    
    /**
     * Creates the cell.
     *
     * @param file the file
     * @param fsv the fsv
     * @return the cmis thumbnail cell
     */
    protected CmisThumbnailCell createCell(CmisObject file,FileSystemView fsv) {
        CmisThumbnailCell cell = new CmisThumbnailCell(this);
        updateCell(cell,file,fsv);
        return cell;
    }
    
    /**
     * Update cell.
     *
     * @param cell the cell
     * @param file the file
     * @param fsv the fsv
     */
    protected void updateCell(CmisThumbnailCell cell,CmisObject file,FileSystemView fsv) {
        cell.setText(file.getName());
        cell.setImageRescale(1.5, 1.5);

        if (BaseTypeId.CMIS_FOLDER.equals(file.getBaseTypeId())) {
        	cell.setImage(ImageUtilities.iconToBufferedImage(IconFactory.getFolderIcon(this)));
        }else {
        	cell.setImage(ImageUtilities.iconToBufferedImage(IconFactory.getIcon(((Document)file).getContentStreamMimeType(),this))); 
        }
        
    }
    
    /**
     * Load file image.
     *
     * @param cell the cell
     * @param file the file
     */
    private void loadFileImage(CmisThumbnailCell cell, File file) {
        if (isEnabled() == false) {
            return;
        }
        
        File imgFile = file;
        File cacheFile = null;
        
        if (useCache == true) {
            // get the file to load whether cached or original file
            md5.update(file.toURI().toString().getBytes());
            byte[] bytes = md5.digest();
            String md5String  = ShellUtilities.bytesToHexString(bytes) + ".png";
            cacheFile = new File(cacheDirectory,md5String);
            try {
                if (isCacheValid(imgFile,cacheFile)) {
                    imgFile = cacheFile;
                }
            } catch (Exception e) {}
        }
        
        ImageReader reader = null;
        BufferedImage img = null;
        try {
            reader = getImageReader(imgFile);
            img = cell.readFileImage(reader, null);
            // to make the loading faster, don't need to load the whole image
            //ImageReadParam param = reader.getDefaultReadParam();
            //param.setSourceProgressivePasses(0,4);
            //cell.readFileImage(reader, param);
        } catch (IOException e) {
        } finally {
            if (reader != null) {
                if (reader.getInput() instanceof ImageInputStream) {
                    try {
                        ((ImageInputStream)reader.getInput()).close();
                    }   catch (Exception e) { }
                }
                reader.dispose();
                reader = null;
            }
        }
        
        if (useCache == true && !imgFile.equals(cacheFile) && img != null) {
            if (!(img.getWidth() <= 128 && img.getHeight() <= 128)) {
                try {
                    BufferedImage smallImg = cell.getImage();
                    writeImageCache(smallImg,cacheFile, file.lastModified(), file.toURI());
                    //ImageIO.write(img,"png",cacheFile);
                } catch (Exception ex) {}
            }
        }
    }
    
    /**
     * Checks if is cache valid.
     *
     * @param file the file
     * @param cacheFile the cache file
     * @return true, if is cache valid
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private boolean isCacheValid(File file,File cacheFile) throws IOException {
        boolean valid = false;
        
        if (cacheFile.exists() == false) {
            return valid;
        }
        
        ImageInputStream iis = ImageIO.createImageInputStream(cacheFile);
        @SuppressWarnings("rawtypes")
		Iterator readers = ImageIO.getImageReadersBySuffix("png");
        ImageReader reader = (ImageReader)readers.next();
        reader.setInput(iis, true);
        
        String cacheMTime = null;
        String cacheURI = null;
        
        IIOMetadata metadata = reader.getImageMetadata(0);
        IIOMetadataNode root = (IIOMetadataNode)metadata.getAsTree(metadata.getNativeMetadataFormatName());
        NodeList nodeList = root.getElementsByTagName("tEXtEntry");
        for (int i=0; i<nodeList.getLength(); i++) {
            IIOMetadataNode node = (IIOMetadataNode)nodeList.item(i);
            if (node.getAttribute("keyword").equals("Thumb::MTime")) {
                cacheMTime = node.getAttribute("value");
            }
            if (node.getAttribute("keyword").equals("Thumb::URI")) {
                cacheURI = node.getAttribute("value");
            }
        }
        
        try {
            iis.close();
        } catch (Exception e) { }
        
        if (cacheMTime == null || cacheURI == null) {
            return valid;
        }
        
        String mTime = Long.toString(file.lastModified());
        String uri = file.toURI().toString();
        if (cacheURI.equals(uri) && cacheMTime.equals(mTime)) {
            valid = true;
        }
        
        return valid;
    }
    
    /**
     * Write image cache.
     *
     * @param bi the bi
     * @param cacheFile the cache file
     * @param lastModified the last modified
     * @param uri the uri
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void writeImageCache(BufferedImage bi, File cacheFile, long lastModified, URI uri) throws IOException {
        ImageWriter writer = null;
        if (imageReaderWriterPreferences != null) {
            writer = imageReaderWriterPreferences.getPreferredImageWriterByFormatName("png");
        }
        if (writer == null) {
            @SuppressWarnings("rawtypes")
			Iterator writers = ImageIO.getImageWritersByFormatName("png");
            writer = (ImageWriter)writers.next();
        }
        
        // set up metadata
        IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(bi),null);
        Node node = metadata.getAsTree(metadata.getNativeMetadataFormatName());
        Node tEXtNode = new IIOMetadataNode("tEXt");
        node.appendChild(tEXtNode);
        Element entryElt;
        entryElt = new IIOMetadataNode("tEXtEntry");
        entryElt.setAttribute("keyword", "Software");
        entryElt.setAttribute("value", "Kiyut Ekspos Image Viewer");
        tEXtNode.appendChild(entryElt);
        entryElt = new IIOMetadataNode("tEXtEntry");
        entryElt.setAttribute("keyword", "Thumb::MTime");
        entryElt.setAttribute("value", Long.toString(lastModified));
        tEXtNode.appendChild(entryElt);
        entryElt = new IIOMetadataNode("tEXtEntry");
        entryElt.setAttribute("keyword", "Thumb::URI");
        entryElt.setAttribute("value", uri.toString());
        tEXtNode.appendChild(entryElt);
        metadata.mergeTree(metadata.getNativeMetadataFormatName(), node);
        
        // create temp file
        File parentFile = cacheFile.getParentFile();
        File tmpFile = new File(parentFile, "temp" + Runtime.getRuntime().hashCode() + ".png");
        
        // write to temp file
        ImageOutputStream ios = ImageIO.createImageOutputStream(tmpFile);
        writer.setOutput(ios);
        IIOImage iioImage = new IIOImage(bi,null,metadata);
        writer.write(null,iioImage,null);
        ios.close();
        writer.dispose();
        
        // rename temp file to cache file
        tmpFile.renameTo(cacheFile);
    }
    
    /**
     * Gets the image reader.
     *
     * @param file the file
     * @return the image reader
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private ImageReader getImageReader(File file) throws IOException {
        ImageInputStream iis = ImageIO.createImageInputStream(file);
        
        String suffix = ShellUtilities.getFileSuffix(file);
        ImageReader reader = null;
        if (imageReaderWriterPreferences != null) {
            reader = imageReaderWriterPreferences.getPreferredImageReaderBySuffix(suffix);
        }
        if (reader == null) {
            @SuppressWarnings("rawtypes")
			Iterator readers = ImageIO.getImageReadersBySuffix(suffix);
            reader = (ImageReader)readers.next();
        }
        reader.setInput(iis,true);
        
        return reader;
    }
    
    /* (non-Javadoc)
     * @see fr.umlv.cmis.listview.ViewComponent#getViewModel()
     */
    public CmisObjectListViewModel getViewModel() {
        return dataModel;
    }
    
    /**
     * Sets the selection model.
     *
     * @param selectionModel the new selection model
     */
    public void setSelectionModel(ListSelectionModel selectionModel) {
        if (selectionModel == null) {
            throw new IllegalArgumentException("selectionModel must be non null");
        }
        
        this.selectionModel = selectionModel;
        this.selectionModel.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onListSelectionChanged(e);
            }
        });
    }
    
    /* (non-Javadoc)
     * @see fr.umlv.cmis.listview.ViewComponent#getSelectionModel()
     */
    public ListSelectionModel getSelectionModel() {
        return selectionModel;
    }
    
    /**
     * Checks if is selected index.
     *
     * @param index the index
     * @return true, if is selected index
     */
    public boolean isSelectedIndex(int index) {
        return getSelectionModel().isSelectedIndex(index);
    }
    
    /**
     * Calculate preferred size.
     */
    protected void calculatePreferredSize() {
        if ( (cells.size() > 0) && (this.isVisible()) ) {
            CmisThumbnailCell cell = cells.get(0);
            double width = getVisibleRect().getWidth();
            columnCount = (int)(width / (cell.getPreferredSize().getWidth() + flowLayout.getVgap()));
            if (columnCount > 0) {
                int row = (int)(cells.size()/columnCount) + 1;
                double height = row * (cell.getPreferredSize().getHeight() + flowLayout.getHgap());
                Dimension dim = new Dimension((int)width,(int)height);
                setPreferredSize(dim);
            }
        }
    }
    
    /**
     * Checks if is editing.
     *
     * @return true, if is editing
     */
    public boolean isEditing() {
        boolean b = false;
        CmisObjectListViewModel dataModel = getViewModel();
        if (dataModel.getState() == CmisObjectListViewModel.EDIT) { b = true; }
        return b;
        
    }
    
    /**
     * Gets the cell editor.
     *
     * @return the cell editor
     */
    public ThumbnailCellEditor getCellEditor() {
        return cellEditor;
    }
    
    /**
     * Sets the editing index.
     *
     * @param index the new editing index
     */
    private void setEditingIndex(int index) {
        editingIndex = index;
    }
    
    /**
     * Gets the editing index.
     *
     * @return the editing index
     */
    public int getEditingIndex() {
        return editingIndex;
    }
    
    /**
     * Edits the cell at.
     *
     * @param index the index
     * @return true, if successful
     */
    public boolean editCellAt(int index) {
        boolean b = false;
        
        if ( (index < 0) || (index >= dataModel.getRowCount()) ) {
            throw new IndexOutOfBoundsException("invalid index");
        }
        
        selectionModel.clearSelection();
        selectionModel.setSelectionInterval(index,index);
        setEditingIndex(index);
        
        CmisObjectListViewModel dataModel = getViewModel();
        Object value = dataModel.getFile(index);
        JComponent editorComp = cellEditor.getThumbnailCellEditorComponent(this, value, true, index);
        CmisThumbnailCell cell = cells.get(index);
        cell.setCellEditor(editorComp);
        cell.startCellEditing();
        cell.validate();
        cell.repaint();
        
        b = true;
        
        return b;
    }
    
    /**
     * Removes the editor.
     */
    public void removeEditor() {
        if ((editingIndex>=0) && (editingIndex<cells.size())) {
            CmisThumbnailCell cell = cells.get(editingIndex);
            cell.stopCellEditing();
            cell.removeCellEditor();
            editingIndex = -1;
            cell.validate();
        }
    }
    
    /**
     * Editing canceled.
     *
     * @param evt the evt
     */
    public void editingCanceled(ChangeEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        dataModel.setState(CmisObjectListViewModel.BROWSE);
        removeEditor();
    }
    
    /**
     * Editing stopped.
     *
     * @param evt the evt
     */
    public void editingStopped(ChangeEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        dataModel.setState(CmisObjectListViewModel.BROWSE);
        removeEditor();
    }
    
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
     */
    public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int)
     */
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        int i = 64;
        return i;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
     */
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
     */
    public boolean getScrollableTracksViewportWidth() {
        return true;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
     */
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return getScrollableBlockIncrement(visibleRect,orientation,direction);
    }
    
    /**
     * Sets the use cache.
     *
     * @param useCache the new use cache
     */
    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
    }
    
    /**
     * Checks if is use cache.
     *
     * @return true, if is use cache
     */
    public boolean isUseCache() {
        return this.useCache;
    }
    
    /**
     * Sets the cache directory.
     *
     * @param cacheDirectory the new cache directory
     */
    public void setCacheDirectory(File cacheDirectory) {
        if (cacheDirectory.isDirectory() == false) {
            throw new IllegalArgumentException(cacheDirectory.toString() + " is not a directory.");
        }
        
        this.cacheDirectory = cacheDirectory;
    }
    
    /**
     * Gets the cache directory.
     *
     * @return the cache directory
     */
    public File getCacheDirectory() {
        return this.cacheDirectory;
    }
    
    /**
     * Sets the image reader writer preferences.
     *
     * @param prefs the new image reader writer preferences
     */
    public void setImageReaderWriterPreferences(ImageReaderWriterPreferences prefs) {
        this.imageReaderWriterPreferences = prefs;
    }
    
    /**
     * Gets the image reader writer preferences.
     *
     * @return the image reader writer preferences
     */
    public ImageReaderWriterPreferences getImageReaderWriterPreferences() {
        return imageReaderWriterPreferences;
    }
    
    //////////////////////////
    // event handling
    /////////////////////////
    
    /**
     * On mouse clicked.
     *
     * @param evt the evt
     */
    private void onMouseClicked(MouseEvent evt) {
        if (isEnabled() == false) { return; }
        
        requestFocusInWindow();
        
        if (evt.getClickCount() > 1) {
            return;
        }
        
        Component cell = getComponentAt(evt.getX(),evt.getY());
        
        if ( (evt.getClickCount() == 1) && (cell != null) ) {
            int index = cells.indexOf(cell);
            if (evt.isShiftDown()) {
                selectionModel.addSelectionInterval(selectionModel.getLeadSelectionIndex(),index);
            } else if (evt.isControlDown()) {
                selectionModel.addSelectionInterval(index,index);
            } else {
                //selectionModel.clearSelection();
                selectionModel.setSelectionInterval(index, index);
            }
        }
    }
    
    /**
     * On key pressed.
     *
     * @param evt the evt
     */
    private void onKeyPressed(KeyEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        if (dataModel.getState()!=CmisObjectListViewModel.BROWSE) { return; }
        
        int index = getSelectionModel().getMaxSelectionIndex();
        int keyCode = evt.getKeyCode();
        if (keyCode==KeyEvent.VK_RIGHT) {
            index = index + 1;
        } else if (keyCode==KeyEvent.VK_LEFT) {
            index = index - 1;
        } else if (keyCode==KeyEvent.VK_UP) {
            index = index - columnCount;
        } else if (keyCode==KeyEvent.VK_DOWN) {
            index = index + columnCount;
        } else {
            return;
        }
        
        if ((index<0) || (index >= cells.size())) {
            return;
        }
        
        Component cell = (Component)cells.get(index);
        MouseEvent mouseEvent = new MouseEvent(this,MouseEvent.MOUSE_CLICKED,
                evt.getWhen(),evt.getModifiers(),cell.getX(),cell.getY(),1,false,MouseEvent.BUTTON1);
        
        onMouseClicked(mouseEvent);
    }
    
    /**
     * On contents changed.
     *
     * @param e the e
     */
    private void onContentsChanged(ListDataEvent e) {
        // changeAll
        if ((e.getIndex0()==CmisObjectListViewModel.ALL_INDEX) &&  (e.getIndex1()==CmisObjectListViewModel.ALL_INDEX) ){
            refresh();
            return;
        }
        
        FileSystemView fsv = dataModel.getFileSystemView();
        for(int i=e.getIndex0(); i<=e.getIndex1(); i++) {
            CmisObject file = (CmisObject)dataModel.getFile(i);
            CmisThumbnailCell cell = cells.get(i);
            updateCell(cell,file,fsv);
            loadList.add(file);
        }
        
        /*int index = e.getIndex1();
        if (dataModel.getSize() >= index) {
            getSelectionModel().setSelectionInterval(index,index);
        }*/
        
        synchronized(loadList) {
            loadList.notifyAll();
        }
    }
    
    /**
     * On interval added.
     *
     * @param e the e
     */
    private void onIntervalAdded(ListDataEvent e) {
        FileSystemView fsv = dataModel.getFileSystemView();
        for(int i=e.getIndex0(); i<=e.getIndex1(); i++) {
            CmisObject file = (CmisObject)dataModel.getFile(i);
            CmisThumbnailCell cell = createCell(file,fsv);
            this.add(cell,i);
            cells.add(i, cell);
        }
        calculatePreferredSize();
        revalidate();
        repaint();
    }
    
    /**
     * On interval removed.
     *
     * @param e the e
     */
    private void onIntervalRemoved(ListDataEvent e) {
        for(int i=e.getIndex0(); i<=e.getIndex1(); i++) {
            CmisThumbnailCell cell = cells.remove(i);
            this.remove(cell);
        }
        calculatePreferredSize();
        revalidate();
        repaint();
    }
    
    /**
     * On list selection changed.
     *
     * @param e the e
     */
    private void onListSelectionChanged(ListSelectionEvent e) {
        cellEditor.cancelCellEditing();
        
        if (!selectionModel.isSelectionEmpty()) {
            Component comp = (Component)cells.get(selectionModel.getMaxSelectionIndex());
            scrollRectToVisible(comp.getBounds());
        }
        
        for (int i=0; i<cells.size(); i++) {
            CmisThumbnailCell cell = cells.get(i);
            if (selectionModel.isSelectedIndex(i)) {
                cell.setCellHasFocus(true);
            } else {
                cell.setCellHasFocus(false);
            }
            cell.repaint();
        }
    }
    
    
    
    
    /**
     * The Class ThumbnailThread.
     */
    private class ThumbnailThread extends Thread {
        
        /* (non-Javadoc)
         * @see java.lang.Thread#run()
         */
        @SuppressWarnings("static-access")
		public void run() {
            while(true) {
                try {
                    synchronized(loadList) {
                        while ( (loadList.size()==0) || (!isEnabled()) )
                            loadList.wait();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                
                while ((loadList.size() > 0) && (isEnabled())) {
                    try {
                        File file = (File)loadList.remove(0);
                        int indexOf = dataModel.indexOf(file);
                        if (indexOf != -1) {
                            CmisThumbnailCell cell = cells.get(indexOf);
                            if (ImageUtilities.isFileImage(file)) {
                                loadFileImage(cell,file);
                                this.yield();
                            }
                        }
                    } catch(Exception e) {}
                }
            }
        }
    }
    
    /**
     * The Class PropertyChangeHandler.
     */
    private class PropertyChangeHandler implements PropertyChangeListener {
        
        /* (non-Javadoc)
         * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
         */
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            
            if (propertyName.equals("selectionBackground") || propertyName.equals("selectionForeground") ) {
                repaint();
            }
        }
    }
    
    /**
     * The Class ThumbnailViewDragGestureRecognizer.
     */
    protected class ThumbnailViewDragGestureRecognizer extends CustomDragGestureRecognizer {
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDragGestureRecognizer#isDragPossible(java.awt.event.MouseEvent)
         */
        protected boolean isDragPossible(MouseEvent evt) {
            if (super.isDragPossible(evt)) {
                CmisThumbnailView comp = (CmisThumbnailView)evt.getSource();
                if (comp.getDragEnabled()) {
                    Component cell = getComponentAt(evt.getX(),evt.getY());
                    int index = cells.indexOf(cell);
                    if ((index != -1) && comp.isSelectedIndex(index)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    /**
     * The listener interface for receiving thumbnailViewDropTarget events.
     * The class that is interested in processing a thumbnailViewDropTarget
     * event implements this interface, and the object created
     * with that class is registered with a component using the
     * component's <code>addThumbnailViewDropTargetListener<code> method. When
     * the thumbnailViewDropTarget event occurs, that object's appropriate
     * method is invoked.
     *
     * @see ThumbnailViewDropTargetEvent
     */
    protected class ThumbnailViewDropTargetListener extends CustomDropTargetListener {
        
        /** The selected indices. */
        private int[] selectedIndices;
        
        /**
         * Instantiates a new thumbnail view drop target listener.
         *
         * @param j the j
         */
        public ThumbnailViewDropTargetListener(JComponent j) {
			super(j);
		}
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#saveComponentState(javax.swing.JComponent)
         */
        protected void saveComponentState(JComponent comp) {
            CmisThumbnailView viewComp = (CmisThumbnailView)comp;
            CmisObjectListViewSelectionModel sm = (CmisObjectListViewSelectionModel)viewComp.getSelectionModel();
            selectedIndices = sm.getSelectedIndices();
        }
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#restoreComponentState(javax.swing.JComponent)
         */
        protected void restoreComponentState(JComponent comp) {
            CmisThumbnailView viewComp = (CmisThumbnailView)comp;
            CmisObjectListViewSelectionModel sm = (CmisObjectListViewSelectionModel)viewComp.getSelectionModel();
            sm.setSelectedIndices(selectedIndices);
        }
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#updateInsertionLocation(javax.swing.JComponent, java.awt.Point)
         */
        protected void updateInsertionLocation(JComponent comp, Point p) {
            CmisThumbnailView viewComp = (CmisThumbnailView)comp;
            Component cell = getComponentAt((int)p.getX(),(int)p.getY());
            int index = cells.indexOf(cell);
            if (index != -1) {
                viewComp.getSelectionModel().setSelectionInterval(index, index);
            }
        }
    }

	/* (non-Javadoc)
	 * @see fr.umlv.cmis.listview.ViewComponent#getIconSize()
	 */
	@Override
	public int getIconSize() {
		return ICON_SIZE;
	}
}
